from flask import Flask, jsonify, request
import cv2
import cv2.aruco as aruco
import numpy as np
import threading
import requests
import time
import paho.mqtt.client as mqtt
import json
from barcode_scanner_controller import BarcodeScannerController
from daying_api_main import *

app = Flask(__name__)
API_URL = "http://192.168.39.24/api/aruco_data"
detector = None

# MQTT 配置
MQTT_BROKER = "192.168.39.71"
MQTT_PORT = 1883
PROPERTIES_TOPIC = "/devices/67f3887d8b3798bd77ee0039_xiangji1/sys/properties/report"
HEARTBEAT_TOPIC = "/devices/67f3887d8b3798bd77ee0039_xiangji1/sys/messages/up"
COMMAND_TOPIC = "/sys/commands/#"
PROPERTIES_SET_TOPIC = "/sys/properties/set/#"


# 在MQTT配置部分添加响应话题
RESPONSE_TOPIC = "/sys/commands/response/request_id="
PROPERTIES_SET_RESPONSE_TOPIC = "/sys/properties/set/response/request_id="
# 创建 MQTT 客户端
mqtt_client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60)
mqtt_client.loop_start()
#创建扫码类
scanner = BarcodeScannerController()

def on_connect(client, userdata, flags, rc, properties=None):
    if rc == 0:
        print("✅ MQTT111111连接成功")
    else:
        print(f"❌ MQTT1111111连接失败，错误码: {rc}")
    # 订阅控制话题
    mqtt_client.subscribe(f"/devices/67f3887d8b3798bd77ee0039_xiangji1{COMMAND_TOPIC}")
    _topic1 = f"/devices/67f4d7b7eb1760189612677f_SKU_SHIBIE{COMMAND_TOPIC}"
    print(f"topic1: {_topic1}")
    mqtt_client.subscribe(f"/devices/67f4d7b7eb1760189612677f_SKU_SHIBIE{COMMAND_TOPIC}")
    mqtt_client.subscribe(f"/devices/67ca9a973a13438b33550cea_TEST_MAC_ZXMZ{COMMAND_TOPIC}")
    mqtt_client.subscribe(f"/devices/67ca9a973a13438b33550cea_TEST_MAC_ZXMZ{PROPERTIES_SET_TOPIC}")

mqtt_client.on_connect = on_connect

LINEAR_MODULE_API = "http://192.168.39.13:8081"

#指令下发
def on_mqtt_message(client, userdata, msg):
    """处理MQTT控制消息"""
    print(f"{msg.topic} : {msg.payload}")
    try:
        # 命令topic
        commandTopic = COMMAND_TOPIC[:-1]
        property_topic = PROPERTIES_SET_TOPIC[:-1]
        if commandTopic in msg.topic and "response" not in msg.topic:
            request_id = msg.topic.split("/")[-1].split("=")[1]
            device_id = msg.topic.split("/")[2]
            print(f"device_id::{device_id}")
            # params = msg.payload.decode()
            params = json.loads(msg.payload.decode())
            print(f"收到请求ID: {request_id}，请求参数: {params}")
            if not params["requestUrl"]:
                response = {
                     "ok": False,
                     "msg": "未设置请求URL，请求失败"
                 }
            else:
                try:
                    # 修正请求方法检查
                    if params.get("requestMethod") == "POST":
                        res = requests.post(params["requestUrl"], json=params)
                    else:
                        res = requests.get(params["requestUrl"], params=params)

                    res_data = res.json()
                    response = {
                        "ok": res_data.get("code", 500) == 200,
                        "msg": res_data.get("msg", "")
                    }
                except Exception as e:
                    response = {"ok": False, "msg": f"请求处理失败: {str(e)}"}
                # mqtt返回消息
                commandResTopic = f"/devices/{device_id}{RESPONSE_TOPIC}{request_id}"
                print(f"commandResTopic::{commandResTopic}")
                mqtt_client.publish(commandResTopic, json.dumps(response))
        elif property_topic in msg.topic and "response" not in msg.topic:
            request_id = msg.topic.split("/")[-1].split("=")[1]
            device_id = msg.topic.split("/")[2]
            print(f"device_id::{device_id}")
            # params = msg.payload.decode()
            params = json.loads(msg.payload.decode())
            print(f"收到请求ID: {request_id}，请求参数: {params}")
            if not params["requestUrl"]:
                response = {
                     "ok": False,
                     "msg": "未设置请求URL，请求失败"
                 }
            else:
                try:
                    # 修正请求方法检查
                    if params.get("requestMethod") == "POST":
                        res = requests.post(params["requestUrl"], json=params)
                    else:
                        res = requests.get(params["requestUrl"], params=params)

                    res_data = res.json()
                    response = {
                        "ok": res_data.get("code", 500) == 200,
                        "msg": res_data.get("msg", "")
                    }
                except Exception as e:
                    response = {"ok": False, "msg": f"请求处理失败: {str(e)}"}
                # mqtt返回消息
                command_res_topic = f"/devices/{device_id}{PROPERTIES_SET_RESPONSE_TOPIC}{request_id}"
                print(f"commandResTopic::{command_res_topic}")
                mqtt_client.publish(command_res_topic, json.dumps(response))

    except Exception as e:
        print(f"处理MQTT消息时出错: {str(e)}")


# 设置MQTT消息回调
mqtt_client.on_message = on_mqtt_message



#货架检测类
class ArucoDetector:

    def __init__(self, aruco_id_start, aruco_id_end):
        self.Point_status = [[i, i + aruco_id_start, 0] for i in range(aruco_id_end - aruco_id_start)]
        self._running = False
        self.thread = None
        self.heartbeat_thread = None
        self.camera_connected = False
        self.cap = None
        self.camera_reconnect_attempts = 0
        self.MAX_RECONNECT_ATTEMPTS = 5
        self.RECONNECT_DELAY = 5  # 秒

    def check_camera_connection(self):
        """检查相机连接状态，如果断开则尝试重连"""
        if self.cap is None or not self.cap.isOpened():
            self.camera_connected = False
            print("⚠️ 相机连接已断开，尝试重新连接...")
            return self.reconnect_camera()
        return True
    def initialize_camera(self):
        """初始化或重新初始化相机连接"""
        if self.cap is not None:
            self.cap.release()

        self.cap = cv2.VideoCapture(0)
        if self.cap.isOpened():
            self.camera_connected = True
            self.camera_reconnect_attempts = 0
            print("✅ 相机连接成功")
            return True
        else:
            self.camera_connected = False
            print("❌ 无法连接相机")
            return False

    def reconnect_camera(self):
        """尝试重新连接相机"""

        self.camera_connected = False
        if self.cap is not None:
            self.cap.release()
        print("🔄 尝试重新连接相机...")
        while self._running:  # 在运行状态下持续尝试
            try:
                # 尝试初始化相机
                self.cap = cv2.VideoCapture(1)
                if self.cap.isOpened():
                    self.camera_connected = True
                    print("✅ 相机重新连接成功")
                    return True

                print(f"❌ 相机连接失败，{self.RECONNECT_DELAY}秒后重试...")
                time.sleep(self.RECONNECT_DELAY)

            except Exception as e:
                print(f"❌ 相机连接异常: {str(e)}")
                time.sleep(self.RECONNECT_DELAY)

        return False

    def detect_aruco_ids(self):

        if not self.camera_connected:
            if not self.reconnect_camera():
                return {"error": "相机断开连接且重连失败","code":503}, 503

        try:
            ret, frame = self.cap.read()
            if not ret:
                print("❌ 无法读取相机帧，尝试重新连接...")
                if not self.reconnect_camera():
                    return {"error": "无法读取帧且重连失败","code":500}, 500
                return {"error": "相机已重新连接，请重试"}, 503

            aruco_dict = aruco.getPredefinedDictionary(aruco.DICT_4X4_1000)
            parameters = aruco.DetectorParameters()
            corners, ids, rejected = aruco.detectMarkers(frame, aruco_dict, parameters=parameters)

            if ids is not None:
                aruco_ids = [int(id[0]) for id in ids]
                # 更新Point_status
                for point in self.Point_status:
                    point[2] = 0 if point[1] in aruco_ids else 1#1是无料，0是有料
                return {"Point_status": self.Point_status}, 200
            else:
                # 如果没有检测到任何ArUco标记，将所有点状态设为0
                for point in self.Point_status:
                    point[2] = 1
                return {"Point_status": self.Point_status,"code":200}, 200

        except Exception as e:
            print(f"❌ 检测过程中发生错误: {str(e)}")
            if "capture" in str(e).lower() or "camera" in str(e).lower():
                if not self.reconnect_camera():
                    return {"error": f"相机错误: {str(e)}","code":500}, 500
            return {"error": str(e)}, 500

    def format_point_data(self, point_status):
        """将点状态数据格式化为指定的JSON格式"""
        formatted_data = []
        for point in point_status:
            formatted_point = {
                "point_id": point[0],
                "arucoid": point[1],
                "point_status": "1" if point[2] == 1 else "0"#1是满穴位，0是空穴位
            }
            formatted_data.append(formatted_point)
        return {"cave_status": formatted_data}

    def send_heartbeat(self):
        """发送心跳消息"""
        while self._running:
            # 只有当相机连接时才发送心跳
            if self.camera_connected:
                try:
                    heartbeat_msg = {
                        "message": "heartbeat",
                        "timestamp": int(time.time()),
                        "status": "online",
                        "camera_status": "connected"
                    }
                    mqtt_client.publish(HEARTBEAT_TOPIC, json.dumps(heartbeat_msg))
                    print(f"💓 心跳发送成功: {heartbeat_msg}")
                except Exception as e:
                    print(f"❌ 心跳发送失败: {e}")
            else:
                print("⚠️ 相机未连接，跳过心跳发送")
            time.sleep(1)  # 每分钟检查一次

    def aruco_detection_loop(self):
        print("🚀 ArUco 检测线程启动...")
        self._running = True

        # 启动前初始化相机
        if not self.initialize_camera():
            print("❌ 无法初始化相机，进入重连模式")
            self.reconnect_camera()


        while self._running:
            # 每次循环前检查相机连接
            if not self.check_camera_connection():
                self.reconnect_camera()
                time.sleep(2)  # 避免高频重试
                continue
            result, status_code = self.detect_aruco_ids()
            if status_code == 200:  # 确保有数据才发送
                formatted_data = self.format_point_data(result["Point_status"])
                try:
                    # 只有相机连接时才发送
                    if self.camera_connected:
                        mqtt_client.publish(PROPERTIES_TOPIC, json.dumps(formatted_data))
                        print(f"✅ 穴位数据发布成功: {formatted_data}")
                    else:
                        print("⚠️ 相机未连接，跳过数据发布")
                except Exception as e:
                    print(f"❌ 穴位数据发布失败: {e}")
            time.sleep(2)  # 每2秒检测一次

    def start_detection(self):
        if not self.thread or not self.thread.is_alive():
            self.thread = threading.Thread(target=self.aruco_detection_loop, daemon=True)
            self.thread.start()

        # 启动心跳线程
        if not self.heartbeat_thread or not self.heartbeat_thread.is_alive():
            self.heartbeat_thread = threading.Thread(target=self.send_heartbeat, daemon=True)
            self.heartbeat_thread.start()

    def stop_detection(self):
        self._running = False
        if self.cap is not None:
            self.cap.release()
            self.camera_connected = False
        if self.thread:
            self.thread.join()
        if self.heartbeat_thread:
            self.heartbeat_thread.join()


@app.route('/print_shipping_label', methods=['POST'])
def handle_print_request():
    """处理打印请求的API接口"""
    try:
        # 获取JSON数据
        data = request.get_json()

        if not data:
            return jsonify({"status": "error", "message": "No data provided"}), 400
            # 验证必需字段
        validation_result = validate_shipping_data(data)
        if not validation_result["success"]:
            return jsonify({
                "code":400,
                    "status": "error",
                    "message": validation_result["message"]
                }), 400

        # 生成标签
        label_path = generate_shipping_label(data)

        # 打印标签
        success, message = print_label(label_path)

        if success:
            return jsonify({
                "code":200,
                "status": "success",
                "message": message,
                "label_path": label_path
            }), 200
        else:
            return jsonify({
                "code": 500,
                "status": "error",
                "message": message,
                "label_path": label_path
            }), 500

    except Exception as e:
        return jsonify({
            "code": 500,
            "status": "error",
            "message": f"处理请求时发生错误: {str(e)}"
        }), 500

@app.route('/init_detector', methods=['POST'])
def init_detector():
    global detector

    data = request.get_json()
    if not data or 'aruco_id_start' not in data or 'aruco_id_end' not in data:
        return jsonify({"code":400,"error": "缺少aruco_id_start或aruco_id_end参数"}), 400

    aruco_id_start = data['aruco_id_start']
    aruco_id_end = data['aruco_id_end']

    # 如果已有检测器，先停止
    if detector:
        detector.stop_detection()

    # 创建新检测器
    detector = ArucoDetector(aruco_id_start, aruco_id_end)
    detector.start_detection()  # 启动检测线程

    return jsonify({"code":200,"message": "ArucoDetector初始化成功"}), 200


@app.route('/acupoints/status', methods=['GET'])
def handle_acupoints_status():
    global detector

    if detector is None:
        return jsonify({"code":400,"error": "请先调用/init_detector初始化ArucoDetector"}), 400

    result, status_code = detector.detect_aruco_ids()
    if status_code == 200:
        formatted_data = detector.format_point_data(result["Point_status"])
        return jsonify(formatted_data), status_code
    # return jsonify(result), status_code
    return jsonify({"code": status_code, "data": result, "msg": ""}), 200

@app.route('/upBarcode', methods=['GET'])
def handle_up_barcode():
    return scanner.up_barcode()

@app.route('/openScan', methods=['GET'])
def handle_open_scan():
    isOpen = request.args.get('isOpen')
    if isOpen == "1":
        scanner_thread = threading.Thread(target=scanner.run, daemon=True)
        scanner_thread.start()
    else:
        scanner.stop()

    return scanner.open_scan(isOpen)


def validate_shipping_data(data):
    """验证运单数据完整性"""
    # 1. 检查顶层必填字段
    required_fields = {
        'order_id': (str,),  # 订单ID
        'order_count': (int,),  # 订单数量
        'platform': (str,),  # 平台名称
        'items': (list,),  # 商品列表
        'payment': (dict,),  # 支付信息
        'customer': (dict,),  # 客户信息
        'task_info': (dict,)  # 任务信息
    }

    for field, allowed_types in required_fields.items():
        if field not in data:
            return {
                "code": 400,
                "success": False,
                "message": f"缺少必填字段: {field}"
            }
        if not isinstance(data[field], allowed_types):
            return {
                "code": 400,
                "success": False,
                "message": f"字段'{field}'类型错误，应为: {'/'.join([t.__name__ for t in allowed_types])}"
            }

    # 2. 验证支付信息
    payment_required = {
        'total': (float, int),
        'actual_pay': (float, int)
    }

    for field, allowed_types in payment_required.items():
        if field not in data['payment']:
            return {
                "code": 400,
                "success": False,
                "message": f"支付信息中缺少字段: {field}"
            }
        if not isinstance(data['payment'][field], allowed_types):
            return {
                "code": 400,
                "success": False,
                "message": f"支付字段'{field}'类型错误，应为: {'/'.join([t.__name__ for t in allowed_types])}"
            }

    # 3. 计算并验证商品总额
    calculated_total = 0.0
    for item in data['items']:
        if 'price' not in item or 'quantity' not in item:
            return {
                "code": 400,
                "success": False,
                "message": f"商品{item.get('name', '')}缺少price或quantity字段"
            }
        if not isinstance(item['price'], (float, int)) or not isinstance(item['quantity'], int):
            return {
                "code": 400,
                "success": False,
                "message": f"商品{item.get('name', '')}的price或quantity类型错误"
            }
        if item['quantity'] <= 0:
            return {
                "code": 400,
                "success": False,
                "message": f"商品{item.get('name', '')}的数量必须大于0"
            }
        calculated_total += item['price'] * item['quantity']

    calculated_total = round(calculated_total, 2)
    provided_total = round(data['payment']['total'], 2)

    if abs(calculated_total - provided_total) > 0.01:
        return {
            "code": 400,
            "success": False,
            "message": f"支付总额不匹配，计算值:{calculated_total}，提供值:{provided_total}"
        }

    # 4. 验证客户信息
    customer_required = {
        'name': (str,),
        'phone': (str,),
        'address': (str,)
    }

    for field, allowed_types in customer_required.items():
        if field not in data['customer']:
            return {
                "code": 400,
                "success": False,
                "message": f"客户信息中缺少字段: {field}"
            }
        if not isinstance(data['customer'][field], allowed_types):
            return {
                "code": 400,
                "success": False,
                "message": f"客户字段'{field}'类型错误，应为: {'/'.join([t.__name__ for t in allowed_types])}"
            }

    # 5. 验证任务信息
    task_required = {
        'task_id': (int, str),
        'robot_name': (str,)
    }

    for field, allowed_types in task_required.items():
        if field not in data['task_info']:
            return {
                "code": 400,
                "success": False,
                "message": f"任务信息中缺少字段: {field}"
            }
        if not isinstance(data['task_info'][field], allowed_types):
            return {
                "code": 400,
                "success": False,
                "message": f"任务字段'{field}'类型错误，应为: {'/'.join([t.__name__ for t in allowed_types])}"
            }

    # 6. 验证商品列表
    if len(data['items']) == 0:
        return {
            "code": 400,
            "success": False,
            "message": "商品列表不能为空"
        }

    item_required = {
        'name': (str,),
        'price': (float, int),
        'quantity': (int,)
    }

    for idx, item in enumerate(data['items'], 1):
        for field, allowed_types in item_required.items():
            if field not in item:
                return {
                    "code": 400,
                    "success": False,
                    "message": f"商品#{idx} 缺少字段: {field}"
                }
            if not isinstance(item[field], allowed_types):
                return {
                    "code": 400,
                    "success": False,
                    "message": f"商品#{idx} 字段'{field}'类型错误，应为: {'/'.join([t.__name__ for t in allowed_types])}"
                }
        if item['quantity'] <= 0:
            return {
                "code": 400,
                "success": False,
                "message": f"商品#{idx} 数量必须大于0"
            }

    return {
        "code": 200,
        "success": True,
        "message": "数据验证通过"
    }

def init():
    global detector
    # 如果已有检测器，先停止
    if detector:
        detector.stop_detection()
    # 创建新检测器
    detector = ArucoDetector(0, 9)
    detector.start_detection()  # 启动检测线程

if __name__ == '__main__':
    init()
    app.run(host='0.0.0.0', port=8081, debug=True)
